import java.util.ArrayList;
import java.util.concurrent.LinkedBlockingQueue; // for channels
import java.io.BufferedReader;
import java.io.FileReader;
import java.math.BigInteger;
import java.util.Iterator;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.io.FileWriter;

public class ISASimulator {

     public static final int D_MEM_SIZE = 8192;
     public static final int I_MEM_SIZE = 8192;
     public static final int NUM_CHANNELS = 4;

     // constant values that depends on your ISA
     // TODO: modify this
     public static final int REG_FILE_SIZE = 16;
     public static final int OPCODE_LENGTH = 5;
     public static final int REG_LENGTH = 4;
     public static final int IMM_I = 12;
     public static final int IMM_J = 8;
     public static final int IMM_12 = 12;
     public static final int IMM_8 = 8;
     public static final int IMM_REG = 4;
     public static final int STACK_POINTER = 2;
     public static final int FRAME_POINTER = 3;
     public static final int LOCAL1 = 10;
     public static final int LOCAL2 = 11;
     public static final int LOCAL3 = 12;
     public static final int LOCAL4 = 13;
     public static final int RETURN_ADDRESS_REG = 5;
     public static final int IMMEDIATE_REG = 4;
     public static final int PROGRAM_COUNTER = 1;

     private int channel_buffer_size;

     private int PC; // current program counter
     private int inst_count; // number of instructions we have executed for the simulator

     private String[] inst_mem; // inst_mem is kept in a string for easier parsing of opcode/operands/etc
     private Int34[] data_mem;

     private Int34[] reg_file;

     private ArrayList<LinkedBlockingQueue<Int34>> channels; //Array  of Linked Lists (LL) which each list acting as a channel.

     public static FileWriter writer;
     

     // default constructor
     public ISASimulator() { 
	  // init PC to 0
	  PC = 0;

	  channel_buffer_size = 16; // set default channel buffer size

	  // initialize memories
	  inst_mem = new String[I_MEM_SIZE];
	  data_mem = new Int34[D_MEM_SIZE];

	  initMem(true);
	  initMem(false);

	  // initialize register file
	  reg_file = new Int34[REG_FILE_SIZE];

	  initRegFile(); // clear registers

	  // init channels
	  channels = new ArrayList<LinkedBlockingQueue<Int34>>(NUM_CHANNELS);
	  for(int i = 0; i < NUM_CHANNELS; ++i)
	       channels.add(new LinkedBlockingQueue<Int34>());

	  //clearChannels();
     }

     // reset everythinig to it's initial state (i.e. memory/registers/channels cleared and PC = 0)
     public void resetSimulator() {
	  PC = 0;

	  // reset memory
	  clearMem(true);
	  clearMem(false);

	  // reset register file
	  clearRegFile();

	  // make sure we have the default channel size
	  channel_buffer_size = 16;
	  setBufferSize(channel_buffer_size); // this also clears the channels
     }

     // initialize the memory (create and zero out)
     public void initMem(boolean imem) {
	  if(imem) { for(int i = 0; i < I_MEM_SIZE; ++i) inst_mem[i] = new String("00000000000000000"); }
	  else { for(int i = 0; i < D_MEM_SIZE; ++i) data_mem[i] = new Int34(0L); }
     }

     // clears the memory: imem = true means inst memory, false = data mem
     public void clearMem(boolean imem) {
	  if(imem) {
	       for(int i = 0; i < I_MEM_SIZE; ++i) inst_mem[i] = "00000000000000000";
	  }
	  else {
	       for(int i = 0; i < D_MEM_SIZE; ++i) data_mem[i].create(0,0L);
	  }
     }

     // init the reg files to all 0's
     public void initRegFile() { for(int i = 0; i < REG_FILE_SIZE; ++i) reg_file[i] = new Int34(0L); }

     // clears the register file
     public void clearRegFile() {
	  for(int i = 0; i < REG_FILE_SIZE; ++i)
	       reg_file[i].create(0,0L);
     }

     // clears all channels
     public void clearChannels() {
	  for(int i = 0; i < NUM_CHANNELS; ++i)
	       clearChannel(i);
     }

     // clear a specific channel (chan_num)
     public void clearChannel(int chan_num) {
	  // sanity check on input
	  if(chan_num < 0 || chan_num >= NUM_CHANNELS) {
	       System.out.println("Invalid channel number: " + chan_num);
	       return;
	  }

	  channels.get(chan_num).clear();
     }

     /*
      * Description: loads either imem (imem = true) or dmem with a COE file (starting at address "start_addr")
      *
      * Returns: true if successful, false otherwise
      *
      * Note: dmem expects the input to be in 2's complement format... this is a bit hackish as sometimes the memory location will
      * be a pointer and therefore not really be 2's complement. However, we can (reasonably) assume that this won't be a
      * problem because the addresses used will never be large enough to have a MSB of 1 (since the memory is only 8K)
      */
     public boolean loadMem(String coe, int start_addr, boolean imem) {
	  int curr_addr = start_addr; // which address we are currently pointing to
	  int curr_line = -1; // line number of the file that is currently read in
	  String line, tmp;
	  int radix = 16; // radix being used... default is 16

	  BufferedReader in = null;

	  // load COE file
	  try { in = new BufferedReader(new FileReader(coe)); }
	  catch(Exception e) { 
	       System.out.println("Couldn't open file: " + coe + ". Load failed."); 
	       return false;
	  }

	  // read in first 2 lines of COE, which have a specific format
	  try {
	       line = in.readLine(); // this line should be MEMORY_INITIALIZATION_RADIX=16
	       curr_line++;
	       if(!line.contains("MEMORY_INITIALIZATION_RADIX=16")) {
		    System.out.println("Expected MEMORY_INITIALIZATION_RADIX=16; on line 0 of file. Load failed.");
		    return false;
	       }

	       // extract the radix value from the string
	       String radix_string = line.split("=")[1];
	       radix_string = radix_string.substring(0,radix_string.length()-1); // strip off the trailing ";"
	       radix = Integer.valueOf(radix_string).intValue(); // convert to int

	       // only support binary and hex for now
	       if(radix != 2 && radix != 16) {
		    System.out.println("Radix format must be 2 (binary) or 16 (decimal). Load failed.");
		    return false;
	       }

	       line = in.readLine(); // this line should be MEMORY_INITIALIZATION_VECTOR=
	       curr_line++;
	       if(!line.contentEquals("MEMORY_INITIALIZATION_VECTOR=")) {
		    System.out.println("line 1 must be \"MEMORY_INITIALIZATION_VECTOR=\". Load failed.");
		    return false;
	       }
	  }
	  catch(Exception e) {
	       System.out.println("Error reading input file. Load failed.");
	       return false;
	  }
		
	  // loop through file and get all the info
	  try {
	       line = in.readLine();
	       curr_line++;

	       while(line != null) { // stop when we read EOF
		    if((imem && curr_addr >= I_MEM_SIZE) || (!imem && curr_addr >= D_MEM_SIZE)) {
			 System.out.println("Too many addresses specified in COE file. Load failed.");
			 return false;
		    }

		    // if imem then we simply set the mem location as the binary version of the string (minus the trailing ',')
		    // if radix isn't binary, then conversion to binary must take place here must take place here
		    tmp = "";
		    if(imem) {
			 // strip off trailing ',' if there is one (should be EOF soon if not one)
			 if(line.substring(line.length()-1,line.length()).equals(","))
			      line = line.substring(0,line.length()-1);

			 // make sure the string is the correct length (based on radix)
			 if((radix == 2 && line.length() != 17) || (radix == 16 && line.length() != 5)) {
			      System.out.println(line + " (line " + curr_line + ") has incorrect format. Load failed.");
			      return false;
			 }

			 // radix 2 means we just copy the string over to the imem location
			 if(radix == 2) {
			      inst_mem[curr_addr] = line;
			      curr_addr++;
			 }
			 else { // need to convert each digit to binary string first
			      // since our instruction length is not a multiple of 4, the first hex digit must be 0 or 1
			      if(line.charAt(0) != '0' && line.charAt(0) != '1') {
				   System.out.println(line + " (line " + curr_line + "): First hex value must be 0 or 1. Load failed.");
				   return false;
			      }
			      else { // tack on first value
				   if(line.charAt(0) == '0') tmp = "0";
				   else tmp = "1";
			      }

			      String hexString = "";
			      // loop through and convert all the values
			      for(int i = 1; i < line.length(); ++i) {
				   hexString = hexToBin(line.charAt(i));
				   if(hexString.length() == 0) { // make sure the value was 0 - f and not some random char
					System.out.println(line + " (line " + curr_line + "): Non-Hex value encountered. Load failed.");
					return false;
				   }
				   tmp += hexString;
			      }

			      inst_mem[curr_addr] = tmp;
			      curr_addr++;
			 }
		    }

		    else { // convert to Int34 before loading into dmem
			 boolean isPositive = true; // is this a positive value?

			 line = line.substring(0,line.length()-1); // strip off the trailing ','

			 // make sure the string is the correct length (based on radix)
			 if((radix == 2 && line.length() != 34) || (radix == 16 && line.length() != 9)) {
			      System.out.println(line + " (line " + curr_line + ") has incorrect format. Load failed.");
			      return false;
			 }

			 // radix 2 means we just copy the string over to the imem location
			 if(radix == 2)
			      tmp = line;

			 else { // need to convert each digit to binary string first
			      // manually convert the first digit
			      // if first binary digit is 0, then it is a positive number, else it is negative
			      if(line.charAt(0) == '0') { tmp = "00"; isPositive = true; }
			      else if(line.charAt(0) == '1') { tmp = "01"; isPositive = true; }
			      else if(line.charAt(0) == '2') { tmp = "10"; isPositive = false; }
			      else if(line.charAt(0) == '3') { tmp = "11"; isPositive = false; }
			      else {
				   System.out.println(line + " (line " + curr_line + "): First hex value must be 0, 1, 2, or 3. Load failed.");
				   return false;
			      }

			      String hexString = "";
			      // loop through and convert all the values
			      for(int i = 1; i < line.length(); ++i) {
				   hexString = hexToBin(line.charAt(i));
				   if(hexString.length() == 0) { // make sure the value was 0 - f and not some random char
					System.out.println(line + " (line " + curr_line + "): Non-Hex value encountered. Load failed.");
					return false;
				   }
				   tmp += hexString;
			      }

			 }

			 Int34 line_value;

			 // if the number was positive, we can just convert string directly to big integer
			 if(isPositive) {
			      BigInteger thisNum = new BigInteger(tmp,2);
			      line_value = new Int34(thisNum.signum(),thisNum.longValue());
			 }
			 // if negative, we first need to do twos complement
			 else {
			      boolean flip = false;
			      String twos_comp = "";
			      // leave all LSBs up to the first '1' unchanged, then flip them all
			      for(int i = tmp.length()-1; i >= 0; --i) {
				   if(!flip) {
					twos_comp = tmp.charAt(i) + twos_comp;
					if(tmp.charAt(i) == '1') flip = true; // start flipping from now on
				   }
				   else if(tmp.charAt(i) == '0') twos_comp = '1' + twos_comp;
				   else twos_comp = '0' + twos_comp;
			      }

			      // tack on a negative sign
			      twos_comp = "-" + twos_comp;

			      //BigInteger thisNum = new BigInteger(twos_comp,2);
			      //line_value = new Int34(thisNum.signum(),thisNum.longValue());
			      line_value = new Int34(Long.parseLong(twos_comp,2));
			 }

			 data_mem[curr_addr] = line_value;
			 curr_addr++;
		    }

		    line = in.readLine(); // read in next line
		    curr_line++;
	       } // end while

	       in.close();
	  }
	  catch(Exception e) {
	       System.out.println("Error reading input file. Load failed.");
	       return false;
	  }

	  return true;
     }

     // given a character (0 - f) returns a string of the binary representation of that num
     public String hexToBin(char hex_char) {
	  String result;
	  switch(Character.digit(hex_char,16)) {
	  case 0:
	       result = "0000";
	       break;
	  case 1:
	       result = "0001";
	       break;
	  case 2:
	       result = "0010";
	       break;
	  case 3:
	       result = "0011";
	       break;
	  case 4:
	       result = "0100";
	       break;
	  case 5:
	       result = "0101";
	       break;
	  case 6:
	       result = "0110";
	       break;
	  case 7:
	       result = "0111";
	       break;
	  case 8:
	       result = "1000";
	       break;
	  case 9:
	       result = "1001";
	       break;
	  case 10:
	       result = "1010";
	       break;
	  case 11:
	       result = "1011";
	       break;
	  case 12:
	       result = "1100";
	       break;
	  case 13:
	       result = "1101";
	       break;
	  case 14:
	       result = "1110";
	       break;
	  case 15:
	       result = "1111";
	       break;
	  default:
	       result = "";
	       break;
	  }
		
	  return result;
     }

     public void printIMem() { printIMem(0, I_MEM_SIZE-1); }
     public void printDMem() { printIMem(0, D_MEM_SIZE-1); }

     // disassembles instructions starting at start_addr and going to start_addr+range
     public void printIMem(int start_addr, int range) {
	  // sanity check of inputs
	  if(range < 0) {
	       System.out.println("Range must be positive.");
	       return;
	  }
	  if(start_addr + range >= I_MEM_SIZE) {
	       System.out.println("startaddr + size must be less than " + I_MEM_SIZE);
	       return;
	  }

	  // loop through and print values
	  for(int i = start_addr; i < start_addr + range; ++i)
	       System.out.println("IMEM[" + i + "]: " + inst_mem[i]);
     }

     // prints data mem contents from start_addr to start_addr+range
     public void printDMem(int start_addr, int range) {
	  // sanity check of inputs
	  if(range < 0) {
	       System.out.println("Range must be non-negative.");
	       return;
	  }
	  if(start_addr + range >= D_MEM_SIZE) {
	       System.out.println("startaddr + size must be less than " + D_MEM_SIZE);
	       return;
	  }

	  // loop through and print values
	  for(int i = start_addr; i <= start_addr + range; ++i)
	       System.out.println("DMEM[" + i + "]: " + data_mem[i]);
     }

     // prints the contents of all the registers
     public void printRegFile() {
      try { writer.write("\n\n");  } 
              catch(Exception e) {
              System.out.println("couln't make a FileWriter");
                 } 
	  for(int i = 0; i < REG_FILE_SIZE; ++i)
       {
      try { writer.write("PC: "+PC+"\n"+i + ":" + reg_file[i]+ "  ");  } 
              catch(Exception e) {
              System.out.println("couln't make a FileWriter");
                 } 

	       System.out.println(i + ": " + reg_file[i]);
       }
     }

     // print out a specific channel
     public void printChannel(int chan_num) {
	  // sanity check on input
	  if(chan_num < 0 || chan_num >= NUM_CHANNELS) {
	       System.out.println("Invalid channel number: " + chan_num);
	       return;
	  }

	  Iterator<Int34> chan_vals = channels.get(chan_num).iterator(); // get an iterator for this buffer

	  int index = 0;

	  while(chan_vals.hasNext()) {
	       System.out.println(index + ": " + chan_vals.next());
	       index++;
	  }
     }

     // set specific register to a specific value
     public void setReg(int reg_num, Int34 value) {
	  // sanity check of input
	  if(reg_num < 0 || reg_num >= REG_FILE_SIZE) {
	       System.out.println("Invalid register number: " + reg_num);
	       return;
	  }

	  reg_file[reg_num] = value;
     }

     // set specific imem location (addr) to a specific value
     public void setIMem(int addr, String value) {
	  // sanity check of input
	  if(addr < 0 || addr >= I_MEM_SIZE) {
	       System.out.println("Address out of range: " + addr);
	       return;
	  }

	  if(!isIMemFormat(value)) {
	       System.out.println("Incorrect format: " + value);
	       return;
	  }

	  inst_mem[addr] = value;
     }

     // makes sure that this is a 17 bit binary number in string format
     public boolean isIMemFormat(String value) {
	  if(value.length() != 17) return false; // not 17 bits
		
	  // see if all chars are 0 or 1
	  for(int i = 0; i < 17; ++i) {
	       if(value.charAt(i) != '0' && value.charAt(i) != '1') return false;
	  }

	  return true;
     }

     // set specific dmem location (addr) to a specific value
     public void setDMem(int addr, Int34 value) {
	  // sanity check of input
	  if(addr < 0 || addr >= D_MEM_SIZE) {
	       System.out.println("Address out of range: " + addr);
	       return;
	  }

	  data_mem[addr] = value;
     }

     // adds a specific value to buffer of specified channel (chan_num)
     // returns true if added successfully, false otherwise
     public boolean addToChannel(int chan_num, Int34 value) {
	  // sanity check of input
	  if(chan_num < 0 || chan_num >= NUM_CHANNELS) {
	       System.out.println("Invalid channel number: " + chan_num);
	       return false;
	  }

	  return channels.get(chan_num).offer(value); // returns false if no room in the buffer
     }
     
     public Int34 readFromChannel(int chan_num) {
	   	  // sanity check of input
	   	  if(chan_num < 0 || chan_num >= NUM_CHANNELS) {
	   	       System.out.println("Invalid channel number: " + chan_num);
	   	       return null;
	   	  }
	
	   	  try {
			return channels.get(chan_num).take();
	   	  } catch (InterruptedException e) {	
			e.printStackTrace();
			return null;
	   	  } // returns false if no room in the buffer
     }

     // resizes the channel buffers. also clears channels (so make sure there isn't anything important there)
     public void setBufferSize(int size) {
	  if(size < 0) {
	       System.out.println("buffer size must be non-negative");
	       return;
	  }

	  channel_buffer_size = size;

	  for(int i = 0; i < NUM_CHANNELS; ++i)
	       channels.get(i).clear();
     }

     // sign extend a string to a given length (assumes (i.e. doesn't check) it is a string of 0's and 1's)
     public String signExtend(String value, int length) {
	  String sign_bit = value.substring(0,1);

	  while(value.length() < length) value = sign_bit + value;

	  return value;
     }

     // converts binary string into an Int34
     // assumes input value is strictly a binary number (doesn't check it)
     public Int34 twosCompValue(String value) {
	  // examine first bit to see if it's positive or negative
	  if(value.charAt(0) == '0') { // positive number
	       return new Int34(Long.parseLong(value,2));
	  }
	  else { // negative number

	       boolean flip = false;
	       String twos_comp = "";
	       // leave all LSBs up to the first '1' unchanged, then flip them all
	       for(int i = value.length()-1; i >= 0; --i) {
		    if(!flip) {
			 twos_comp = value.charAt(i) + twos_comp;
			 if(value.charAt(i) == '1') flip = true; // start flipping from now on
		    }
		    else if(value.charAt(i) == '0') twos_comp = '1' + twos_comp;
		    else twos_comp = '0' + twos_comp;
	       }

	       // tack on a negative sign
	       twos_comp = "-" + twos_comp;

	       return new Int34(Long.parseLong(twos_comp,2));
	  }
     }

     // execute num_insts instruction
     public void execute(int num_insts) {
	  // sanity check of inputs
	  if(num_insts < 1) {
	       System.out.println("Number of instructions must be positive.");
	       return;
	  }

	  int num_done = 0; // number of instructions we have completed so far
	  String curr_inst; // the current instruction
	  String opcode_str; // string representing the opcode
	  int opcode;  // the opcode in integer form (so we can use a case statement)
	  int rd, rs, rt, imm_12, imm_8, returnAddr;
	  Int34 rd34, rs34, rt34, imm_12_34, imm_8_34, rZero, rImm, offset;

	  while(num_done < num_insts) {
	       curr_inst = inst_mem[PC]; // get the next instruction
           System.out.println(" curr instr " + Integer.toHexString(Integer.parseInt(curr_inst,2)));

	       opcode_str = curr_inst.substring(0,OPCODE_LENGTH); // get the op-code bits
	       opcode = Integer.valueOf(opcode_str,2).intValue();
	       
	       //parse out the rd, rs, rt, and imm values.
	       opcode_str = curr_inst.substring(OPCODE_LENGTH, OPCODE_LENGTH+REG_LENGTH);
	       rd = Integer.valueOf(opcode_str,2).intValue();
	       rd34 = reg_file[rd];
	       
	       opcode_str = curr_inst.substring(OPCODE_LENGTH+REG_LENGTH, OPCODE_LENGTH+2*REG_LENGTH);
	       rs = Integer.valueOf(opcode_str,2).intValue();
	       rs34 = reg_file[rs];
	       
	       opcode_str = curr_inst.substring(OPCODE_LENGTH+2*REG_LENGTH, OPCODE_LENGTH+3*REG_LENGTH);
	       rt = Integer.valueOf(opcode_str,2).intValue();
	       rt34 = reg_file[rt];
	       offset = new Int34(rt);
	       
	       opcode_str = curr_inst.substring(OPCODE_LENGTH, OPCODE_LENGTH + IMM_12);
	       imm_12 = Integer.valueOf(opcode_str,2).intValue();
	       imm_12_34 = new Int34(imm_12);
	       
	       opcode_str = curr_inst.substring(OPCODE_LENGTH + REG_LENGTH, OPCODE_LENGTH + REG_LENGTH + IMM_8);
	       imm_8 = Integer.valueOf(opcode_str,2).intValue();
	       imm_8_34 = new Int34(imm_8);
	       
	       rZero = new Int34(0);
	       rImm = reg_file[IMM_REG];

	       // TODO: complete this
         printRegFile();
	       switch (opcode) {
	       case 0: // |00| rd | rs1 | rs2 | 	and  $rd, $rs1, $rs2		R[rd] = R[rs1] & R[rs2]
	    	   // halt check:
	    	   if (Integer.valueOf(curr_inst) == 0)
	    	   {
	    		   num_insts = 0;
	    	   }
	    	   else
	    	   {
	    	   		setReg( rd, rs34.and(rt34) );
	    	   		PC++;
	    	   }
		    break;
	       case 1: // |01| rd | rs1 | rs2 |		or    	$rd, $rs1, $rs2		R[rd] = R[rs1] | R[rs2]
	    	   setReg( rd, rs34.or(rt34) );
	    	   PC++;
		    break;
	       case 2: // |02| rd | rs1 | rs2 |		xor	$rd, $rs1, $rs2		R[rd] = R[rs1] XOR R[rs2]
	    	   setReg( rd, rs34.xor(rt34) );
	    	   PC++;
		    break;
	       case 3: // |03| rd | xxxxxxxx  |		not 	$rd			R[rd] = ~R[rd]
	    	   setReg( rd, rd34.not() );
	    	   PC++;
		    break; 
	       case 4: // |04| rd | xxxxxxxx  |		neg 	$rd			R[rd] = ~R[rd](two's compliment) 
	    	   setReg( rd, rZero.subtract(rd34));
	    	   PC++;
		    break;
	       case 5: // |05| rd | rs1 | rs2 |		add	$rd, $rs1, $rs2		R[rd] = R[rs1] + R[rs2]
	    	   setReg( rd, rs34.add(rt34));
	    	   PC++;
		    break;
	       case 6: // |06| rd | rs1 | rs2 |		sub	$rd, $rs1, $rs2		R[rd] = R[rs1] - R[rs2]
	    	   setReg( rd, rs34.subtract(rt34));
	    	   PC++;
		    break;
	       case 7: 
		    break;
	       case 8: // |08| imm1 |		loadi1	imm1			clears $imm & loads imm1 in bits [11 : 0]
	    	   setReg( IMM_REG, imm_12_34 );
	    	   PC++;
		    break;
	       case 9: // |09| imm2 |		loadi2	imm2			loads imm2 in bits [23 : 12] of $imm
	    	   Int34 mask = new Int34(0xFF000FFF);
	    	   
	    	   imm_12_34 = imm_12_34.shiftLeft(12);
	    	   setReg ( IMM_REG, reg_file[IMM_REG].and(mask));
	    	   System.out.println("after mask: " + reg_file[IMM_REG].toString());
	    	   setReg ( IMM_REG, reg_file[IMM_REG].or(imm_12_34));
	    	   PC++;
		    break;
	       case 10: // |0A|xx| imm3 | 	loadi3	imm3			loads imm3 in bits [33 : 24] of $imm
	    	   Int34 mask2 = new Int34(0x00FFFFFF);
	    	   
	    	   imm_12_34 = imm_12_34.shiftLeft(24);
	    	   setReg ( IMM_REG, reg_file[IMM_REG].and(mask2));
	    	   System.out.println("after mask: " + reg_file[IMM_REG].toString());
	    	   setReg ( IMM_REG, reg_file[IMM_REG].or(imm_12_34));
	    	   
	    	   // bit manipulation for last bit:
	    	   if (imm_12 >= 512)
	    	   {
	    		   Int34 mask5 = new Int34(0x201000000L);
	    		   System.out.println("mask5: "+mask5.toString());
	    		   setReg ( IMM_REG, reg_file[IMM_REG].or(mask5));
	    		   if (imm_12 % 2 == 0)
	    		   {
	    			   Int34 mask6 = new Int34(0x01000000);
	    			   System.out.println("mask6: "+mask6.toString());
	    			   setReg ( IMM_REG, reg_file[IMM_REG].xor(mask6));
	    		   }
	    	   }
	    	   
	    	   PC++;
		    break;
	       case 11: // |0B| imm1 |		loadi1n imm1			fills $imm with 1's & loads imm1 into bits [11 : 0]
	    	   Int34 mask3 = new Int34(0xFFFFFC00);
	    	   mask3 = mask3.shiftLeft(2);
	    	   
	    	   //System.out.println("mask: " + mask3.toString());
	    	   
	    	   setReg( IMM_REG, mask3.or(imm_12_34));
	    	   PC++;
		    break;
	       case 12: // |0C| rd | imm |	addi	$rd, imm		R[rd] = R[d] + imm
	    	   setReg(rd, rd34.add(imm_8_34));	
	    	   PC++;
	    	   break;
	       case 13: // |0D| rd | imm |	subi	$rd, imm		R[rd] = R[d] - imm	
	    	   setReg(rd, rd34.subtract(imm_8_34));	
	    	   PC++;
	    	   break;
	       case 14: // |0E| rd | num | 	sll		$rd, num		R[rd] << num
	    	   setReg(rd, rd34.shiftLeft(imm_8));
	    	   PC++;
		    break;
	       case 15: // |0F| rd | num |  srl 	$rd, num		R[rd] >> num
	    	   setReg(rd, rd34.shiftRight(imm_8));
	    	   PC++;
		    break;
	       case 16: // |10| offset |			bal	 label			PC = PC + offset
	    	   PC = PC + imm_12; 
		    break;
	       case 17: // |11| rc1 | rc2 | rOff |	beq	$rc1, $rc2, $rOff		if (R[rc1] = R[rc2]) then PC = PC + R[rOff]
	    	   if(rd34.equals(rs34)){
	    		   PC = PC + (int)rt34.longValue(); 
	    	   }
	    	   else
	    		   PC++;
	    	break;
	       case 18: // |12| rc1 | rc2 | rOff |	bne	$rc1, $rc2, $rOff		if (R[rc1] != R[rc2]) then PC = PC + R[rOff]
	    	   if(!(rd34.equals(rs34)) ){
	    		   PC = PC + (int)rt34.longValue(); 
	    	   }
	    	   else
	    		   PC++;
	    	break;
	       case 19: // |13| rc1 | rc2 | rOff |	blt	$rc1, $rc2, $rOff		if (R[rc1] < R[rc2]) then PC = PC + R[rOff]
	    	   if(rd34.subtract(rs34).longValue() < 0){
	    		   PC = PC + (int)rt34.longValue(); 
	    	   }
	    	   else
	    		   PC++;
	    	break;
	       case 20: // |14| rc1 | rc2 | rOff |	blteq	$rc1, $rc2, $rOff	if (R[rc1] <= R[rc2]) then PC = PC + R[rOff]
	    	   if(rd34.subtract(rs34).longValue() <= 0){
	    		   PC = PC + (int)rt34.longValue(); 
	    	   }
	    	   else
	    		   PC++;
	    	break;
	       case 21: // |15| raddr | xxxxxxxx |	jmp	LABEL					PC <= R[raddr](current instruction)
	    	   PC = (int) rd34.longValue();
	    	break;  
	       case 22: // |16| raddr | xxxxxxxx |	jal LABEL					$ra => stack, $ra <= PC + 1, PC <= addr
           /*
	    	   setDMem((int)reg_file[STACK_POINTER].longValue(), reg_file[RETURN_ADDRESS_REG]); 	//save return address
	    	   setDMem((int)reg_file[STACK_POINTER].longValue() + 1, reg_file[LOCAL1]);      		//save locals:
	    	   setDMem((int)reg_file[STACK_POINTER].longValue() + 2, reg_file[LOCAL2]);
	    	   setDMem((int)reg_file[STACK_POINTER].longValue() + 3, reg_file[LOCAL3]);
	    	   setDMem((int)reg_file[STACK_POINTER].longValue() + 4, reg_file[LOCAL4]);
	    	   setDMem((int)reg_file[STACK_POINTER].longValue() + 5, reg_file[FRAME_POINTER]);		//save frame pointer

	    	   // set new frame/stack pointer:
	    	   setReg(STACK_POINTER, reg_file[STACK_POINTER].add(new Int34(6)));			  
	    	   setReg(FRAME_POINTER, reg_file[STACK_POINTER]);
	    	   */
	    	   //set new return address and load PC:
	    	   setReg(RETURN_ADDRESS_REG, new Int34(PC+1));
	    	   PC = (int)reg_file[rd].longValue();
	    	   
		    break;
	       case 23: // |17| xxxxxxxxxxxx | 			ret
	    	   PC = (int)reg_file[RETURN_ADDRESS_REG].longValue();								// load PC
	    	   /*
	    	   setReg(STACK_POINTER, reg_file[FRAME_POINTER].subtract(new Int34(6)));				// set stack pointer
               System.out.println("frame pointer " + FRAME_POINTER);
	    	   setReg(LOCAL4, data_mem[(int)reg_file[FRAME_POINTER].longValue() - 2]);			// load locals:
	    	   setReg(LOCAL3, data_mem[(int)reg_file[FRAME_POINTER].longValue() - 3]);
	    	   setReg(LOCAL2, data_mem[(int)reg_file[FRAME_POINTER].longValue() - 4]);
	    	   setReg(LOCAL1, data_mem[(int)reg_file[FRAME_POINTER].longValue() - 5]);*/
	    	   setReg(RETURN_ADDRESS_REG, data_mem[(int)reg_file[FRAME_POINTER].longValue() - 6]);// load return address
	    	   setReg(FRAME_POINTER, data_mem[(int)reg_file[FRAME_POINTER].longValue() - 1]);	// load previous frame pointer
		    break;
	       case 24: // |18| rd | raddr | offest |	load 	$rd, $raddr, offset		R[rd] <= D(R[raddr] + offset)
	    	   setReg( rd, data_mem[(int)(rs34.add(offset).longValue())] );
	    	   PC++;
	    	   //TODO:adjust sp/fp
		   break;
	       case 25: // |19| rd | raddr | offset | 	store  	$rs, $raddr, offest		D(R[raddr] + offset) <= R[rs]
	    	   setDMem((int)(rs34.add(offset).longValue()), rd34);
	    	   PC++;
	    	   //TODO:adjust sp/fp
	    	break;
	       case 26: // |1A| rd | sel |				in 	$rd, sel		R[rd] <= channel[sel]  //there are 16 channels
	    	   setReg( rd, readFromChannel(rt));
	    	   PC++;
		    break;
	       case 27: // |1B| rs | sel |				out	$rs, sel		R[rs] <= channel[sel]  // (15 - 0)
	    	   addToChannel(rt, rd34);
	    	   PC++;
		    break;
	       case 28:
		    break;
	       case 29:
		    break;
	       case 30:
		    break;
	       case 31:
		    break;
	       case 32:
		    break;
	       default:
		    System.err.println("invalid opcode encountered at PC=" + PC);
		    return;
	       }
			
	       setReg(PROGRAM_COUNTER, new Int34(PC));
	       inst_count++; // increase our global counter

	       num_done++; // just finished another instruction
	  }
     }

     // while loop asking for user input for next command
     @SuppressWarnings("unused")
	public void run() {
	  // set up to read user input from console
	  BufferedReader cons = new BufferedReader(new InputStreamReader(System.in));

	  if(cons == null) {
	       System.out.println("No console available. Quitting.");
	       System.exit(1);
	  }

	  /*
	    InputStreamReader input_stream = null;
	    try { input_stream = new InputStreamReader(System.in); }
	    catch(Exception e) {
	    System.out.println("Could not get command line input. Quitting.");
	    System.exit(1);
	    }
	  */

	  String input = null;
	  StringTokenizer input_tokens = null; 
	  String curr_token;

	  while(true) {
	       System.out.print(">> "); // "command prompt"

	       try {
		    input = cons.readLine(); // get input from user
	       } catch (Exception e) {
		    System.out.println("Couldn't read input.  Bye.");
		    System.exit(1);
	       }
	       input_tokens = new StringTokenizer(input); // tokenize the input for easier parsing

	       // make sure it is a valid command and do that command
	       curr_token = input_tokens.nextToken();
	       if(curr_token.equals("iload")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: iload $file_name $start_addr");
		    else {
        /*
			 String file_name = input_tokens.nextToken();
			 int start_addr = Integer.parseInt(input_tokens.nextToken());*/
        String file_name = "garbage_i.coe";
        int start_addr = 0;
        loadMem(file_name,start_addr,true);
		    }
	       }
	       else if(curr_token.equals("dload")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: dload $file_name $start_addr");
		    else {
			 String file_name = input_tokens.nextToken();
			 int start_addr = Integer.parseInt(input_tokens.nextToken());
			 loadMem(file_name,start_addr,false);
		    }
	       }
	       else if(curr_token.equals("go")) {
		    if(input_tokens.countTokens() != 1){ System.out.println("usage: go $number"); 
          System.out.println("within if");
               
         }        
		    else {
			 int number = Integer.parseInt(input_tokens.nextToken());
        try { writer.write("PC: "+PC+"\n"); 
                  System.out.println("witing");}
              catch(Exception e) {
              
              System.out.println("couln't make a FileWriter");
                 } 
			 execute(number);
		    }
	       }
	       else if(curr_token.equals("dump_reg")) {
		    printRegFile();
	       }
	       else if(curr_token.equals("set_reg")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: set_reg $reg_num $value");
		    else {
			 int reg_num = Integer.parseInt(input_tokens.nextToken());
			 Int34 value = new Int34(Long.parseLong(input_tokens.nextToken()));
			 setReg(reg_num,value);
		    }
	       }
	       else if(curr_token.equals("dump_imem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: dump_imem $start_addr $range");
		    else {
			 int start_addr = Integer.parseInt(input_tokens.nextToken());
			 int range = Integer.parseInt(input_tokens.nextToken());
			 printIMem(start_addr,range);
		    }
	       }
	       else if(curr_token.equals("set_imem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: set_imem $addr $value");
		    else {
			 int addr = Integer.parseInt(input_tokens.nextToken());
			 String value = input_tokens.nextToken();
			 setIMem(addr,value);
		    }
	       }
	       else if(curr_token.equals("dump_dmem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: dump_dmem $start_addr $range");
		    else {
			 int start_addr = Integer.parseInt(input_tokens.nextToken());
			 int range = Integer.parseInt(input_tokens.nextToken());
			 printDMem(start_addr,range);
		    }
	       }
	       else if(curr_token.equals("set_dmem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: set_dmem $addr $value");
		    else {
			 int addr = Integer.parseInt(input_tokens.nextToken());
			 Int34 value = new Int34(Long.parseLong(input_tokens.nextToken()));
			 setDMem(addr,value);
		    }
	       }
	       else if(curr_token.equals("dump_channel")) {
		    if(input_tokens.countTokens() != 1) System.out.println("usage: dump_channel $chan_number");
		    else {
			 int chan_num = Integer.parseInt(input_tokens.nextToken());
			 printChannel(chan_num);
		    }
	       }
	       else if(curr_token.equals("put_channel")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: put_channel $chan_number $value");
		    else {
			 int chan_num = Integer.parseInt(input_tokens.nextToken());
			 Int34 value = new Int34(Long.parseLong(input_tokens.nextToken()));
			 addToChannel(chan_num,value);
		    }
	       }
	       else if(curr_token.equals("clear_channel")) {
		    if(input_tokens.countTokens() != 1) System.out.println("usage: clear_channel $chan_number");
		    else {
			 int chan_num = Integer.parseInt(input_tokens.nextToken());
			 clearChannel(chan_num);
		    }
	       }
	       else if(curr_token.equals("set_buf_size")) {
		    if(input_tokens.countTokens() != 1) System.out.println("usage: set_buf_size $size");
		    else {
			 int size = Integer.parseInt(input_tokens.nextToken());
			 setBufferSize(size);
		    }
	       }
	       else if(curr_token.equals("instr_count")) {
		    System.out.println(inst_count + " instructions executed so far");
	       }
	       else if(curr_token.equals("dump_pc")) {
		    System.out.println("current PC is " + PC);
	       }
	       else if(curr_token.equals("reset")) {
		    resetSimulator();
	       }
	       else if( curr_token.equals("exit")) {
		    System.out.println("leaving so soon? ... Bye!");
		    break;
	       }
	       else {
		    System.out.println("unrecognized command.");
	       }
	  }
     }

     public static void main(String[] args) {
	  ISASimulator sim = new ISASimulator();

    try { writer = new FileWriter("results.txt"); }
     catch(Exception e) {
          System.out.println("couln't make a FileWriter");
          } 

	  sim.run(); // run the simulator
  

    try { writer.close(); }
     catch(Exception e) {
          System.out.println("couln't close FileWriter");
          } 
     }
}
